package helper.base

import helper.base.CloneHelper.Cloneable
import helper.base.CloneHelper.Expand.CloneableList.cloneList
import helper.base.ZoneConstraintHelper.CoeffVarList.Expand.toCVs
import helper.base.ZoneConstraintHelper.D1D2Pair.Companion.getD1D2Pair
import helper.base.ZoneConstraintHelper.OneVariable.Expand.toCVs
import helper.base.math.IntRangeHelper
import helper.base.math.IntRangeHelper.toIntRange
import helper.base.math.MathHelper
import helper.base.string.ToStrHelper.IToStr

// 定义一个名为 ZoneConstraintHelper 的对象，包含数学约束条件相关的帮助函数和类
object ZoneConstraintHelper {
    /**
     * 该类的对象一经创建
     * 里面的属性均不能更改
     * CoefficientVariable
     */
    class CoeffVar(
        val coefficient: Double, // 系数
        val variable: String,    // 变量
    ) : Cloneable {
        companion object {
            fun makeCoeffVar(coefficient: Double, variable: String): CoeffVar {
                return CoeffVar(coefficient, variable)
            }

            // 重载的 makeCoeffVar 方法，允许使用任意 Number 类型作为系数
            fun makeCoeffVar(coefficient: Number, variable: String): CoeffVar {
                return makeCoeffVar(coefficient.toDouble(), variable)
            }
        }

        object Expand {
            fun ArrayList<CoeffVar>.toStr(): String {
                return this.joinToString(separator = "") { it.toStr }
            }
        }

        override fun clone() = makeCoeffVar(coefficient, variable)

        // 初始化一个字符串变量 toStr，其值由一个 if 表达式决定
        val toStr: String = if (coefficient == 0.0) {
            // 如果系数 coefficient 等于 0.0，则不需要添加任何系数或变量，直接返回空字符串
            ""
        } else {
            // 如果系数 coefficient 不等于 0.0，则需要构建一个表示该项的字符串
            val sb = StringBuilder() // 使用 StringBuilder 来构建字符串，提高性能

            if (coefficient == -1.0) {
                // 如果系数 coefficient 等于 -1.0，表示该项为负，仅需添加负号
                sb.append("-")
            } else if (coefficient != 1.0) {
                // 如果系数 coefficient 不等于 1.0（且不为 -1.0），需要将系数转换为字符串添加到 sb 中
                // 使用 MathHelper.printDoubleWithoutTrailingZeros 方法去除系数中不必要的尾随零
                // 例如，2.0 将被转换为 "2"
                sb.append(MathHelper.printDoubleWithoutTrailingZeros(coefficient))
            }
            // 无论系数是多少，最后都需要添加变量名称
            // 例如，如果变量是 "x"，则会在这一步添加 "x"
            sb.append(variable)

            // 将 StringBuilder 对象转换为 String 对象并返回
            sb.toString()
        }
    }

    /**
     * 表示一个双重数值对，通常用于表示范围。
     */
    class D1D2Pair(val d1: Double?, val d2: Double?) {
        companion object {
            fun getD1D2Pair(d1: Number?, d2: Number?) = D1D2Pair(d1?.toDouble(), d2?.toDouble())
        }
    }

    // 定义一个接口，表示可以转换为 CoeffVarList 的对象
    interface ICVs

    /**
     * 表示单个变量的类，实现了 ICVs 接口。
     */
    class OneVariable(val string: String) : ICVs {
        companion object {
            fun getOneVariable(string: String) = OneVariable(string)
        }

        object Expand {
            fun String.toCVs() = getOneVariable(this)
        }
    }

    /**
     * 表示 CoeffVar 对象列表的类，实现了 ICVs 接口。
     */
    class CoeffVarList(val list: List<CoeffVar>) : ICVs {
        companion object {
            fun makeCoeffVarList(list: List<CoeffVar>) = CoeffVarList(list)
        }

        object Expand {
            fun List<CoeffVar>.toCVs() = makeCoeffVarList(this)
        }
    }

    // 接口定义了约束条件的基本方法和属性
    interface Constraint : Cloneable, IToStr {
        // 获取、设置点的值，获取变量字符串表示，获取整数范围等方法的定义
        fun getPointD(): Double
        fun getVariablesString(): String
        fun getIntRange(): IntRange
        fun getIfEq1(): Boolean
        fun getIfEq2(): Boolean
        fun getD1D2Pair(): D1D2Pair
        fun cloneCVs(): ICVs
        fun setPointD(number: Number)
        override fun clone(): Constraint
    }

    // V1PointConstraint 和 ZoneConstraint 类的实现，具体实现了 Constraint 接口的方法
    // 这些类用于表示具体的数学约束条件，如点约束或区域约束等

    // 其他方法的定义，如 byFunFindCoeffVar 和 byNameFindCoefficientVariable，用于在约束条件中查找特定的系数变量组合
    class PointConstraint(
        val variable: String,
        var d: Double,
    ) : Constraint {
        companion object {
            fun makePointConstraint(variable: String, d: Double) = PointConstraint(variable, d)
        }

        override fun getPointD(): Double = d

        override fun getVariablesString(): String = variable

        override fun clone(): PointConstraint = makePointConstraint(variable, d)

        override fun toStr(tabNum: Int): String = "${variable}=${d}"

        override fun getIntRange(): IntRange {
            val intD = d.toInt()
            return intD..intD
        }

        override fun getIfEq1(): Boolean = true

        override fun getIfEq2(): Boolean = true

        override fun getD1D2Pair(): D1D2Pair = getD1D2Pair(d, d)

        override fun cloneCVs(): OneVariable = variable.toCVs()

        override fun setPointD(number: Number) {
            this.d = number.toDouble()
        }
    }

    class ZoneConstraint(
        private val cvs: CoeffVarList,
        val aIfEq1: Boolean,
        var d1: Double?,
        var d2: Double?,
        val aIfEq2: Boolean,
    ) : Constraint {
        companion object {
            fun makeZoneConstraint(
                cvs: CoeffVarList,
                ifEq1: Boolean,
                d1: Number?,
                d2: Number?,
                ifEq2: Boolean,
            ): ZoneConstraint {
                return ZoneConstraint(
                    cvs,
                    ifEq1,
                    d1?.toDouble(),
                    d2?.toDouble(),
                    ifEq2,
                )
            }

            fun makeZoneConstraint(
                cvs: List<CoeffVar>,
                ifEq1: Boolean,
                d1: Number?,
                d2: Number?,
                ifEq2: Boolean,
            ): ZoneConstraint {
                return ZoneConstraint(
                    cvs.toCVs(),
                    ifEq1,
                    d1?.toDouble(),
                    d2?.toDouble(),
                    ifEq2,
                )
            }

            fun makePointZoneConstraint(
                cvs: List<CoeffVar>,
                d: Double,
            ): ZoneConstraint {
                return makeZoneConstraint(
                    cvs.toCVs(),
                    true,
                    d,
                    d,
                    true,
                )
            }
        }

        override fun getPointD(): Double = d1!!

        override fun getD1D2Pair(): D1D2Pair = getD1D2Pair(this.d1, this.d2)

        fun isPointZoneConstraint() = aIfEq1 && d1 == d2 && aIfEq2

        override fun setPointD(number: Number) {
            val d = number.toDouble()
            d1 = d
            d2 = d
        }

        override fun getVariablesString(): String {
            return cvs.list.joinToString("") {
                it.toStr
            }
        }

        override fun toStr(tabNum: Int): String {
            return if (cvs.list.isEmpty()) {
                ""
            } else {
                val sb = StringBuilder()
                sb.append(getVariablesString())
                if (isPointZoneConstraint()) {
                    sb.append("=$d1")
                } else {
                    sb.append("∈")
                    sb.append(if (aIfEq1) "[" else "(")
                    sb.append(d1 ?: "-∞")
                    sb.append(",")
                    sb.append(d2 ?: "+∞")
                    sb.append(if (aIfEq2) "]" else ")")
                }
                sb.toString()
            }
        }

        override fun cloneCVs() = cvs.list.cloneList().toCVs()

        override fun getIntRange(): IntRange {
            return run {
                if (d1 == null || d2 == null) {
                    1..0
                } else {
                    IntRangeHelper.getMinMaxIntPair(
                        aIfEq1,
                        d1!!,
                        d2!!,
                        aIfEq2,
                    ).toIntRange()
                }
            }
        }

        override fun getIfEq1() = aIfEq1

        override fun getIfEq2() = aIfEq2

        fun byFunFindCoeffVar(function: (String) -> Boolean): CoeffVar {
            return cvs.list.firstOrNull {
                function(it.variable)
            } ?: throw Exception()
        }

        fun byNameFindCoefficientVariable(name: String): CoeffVar {
            return byFunFindCoeffVar {
                name == it
            }
        }

        override fun clone(): ZoneConstraint {
            return makeZoneConstraint(
                cloneCVs(),
                aIfEq1,
                d1,
                d2,
                aIfEq2,
            )
        }
    }
}